package org.feng.navigation.rest.config;

import cn.hutool.core.util.StrUtil;
import okhttp3.ConnectionPool;
import okhttp3.OkHttpClient;
import org.feng.navigation.common.util.GsonUtil;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.http.MediaType;
import org.springframework.http.client.ClientHttpRequestFactory;
import org.springframework.http.client.OkHttp3ClientHttpRequestFactory;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.StringHttpMessageConverter;
import org.springframework.http.converter.json.GsonHttpMessageConverter;
import org.springframework.web.client.RestTemplate;

import javax.annotation.Resource;
import javax.net.ssl.*;
import java.nio.charset.StandardCharsets;
import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.security.cert.X509Certificate;
import java.util.*;
import java.util.concurrent.TimeUnit;

/**
 * {@link org.springframework.web.client.RestTemplate} 相关的配置<br>
 * <pre>
 * 使用okhttp：OkHttp是一个高效的HTTP客户端，允许所有同一个主机地址的请求共享同一个socket连接；连接池减少请求延时；
 * 透明的GZIP压缩减少响应数据的大小；缓存响应内容，避免一些完全重复的请求
 * 当网络出现问题的时候OkHttp依然坚守自己的职责，它会自动恢复一般的连接问题
 * 如果你的服务有多个IP地址，当第一个IP请求失败时，OkHttp会交替尝试你配置的其他IP，OkHttp使用现代TLS技术(SNI, ALPN)初始化新的连接，当握手失败时会回退到TLS 1.0
 * </pre>
 *
 * @version v1.0
 * @author: junzi
 * @date: 2023年01月15日 00时09分
 */
@Configuration
public class RestTemplateConfig {

    @Resource
    private OkHttpRestTemplateProperties okHttpRestTemplateProperties;

    @Bean
    @Primary
    public RestTemplate restTemplate() {
        ClientHttpRequestFactory factory = new OkHttp3ClientHttpRequestFactory(okHttpClient());
        RestTemplate restTemplate = new RestTemplate(factory);
        // 替换gson
        List<HttpMessageConverter<?>> httpMessageConverterList = restTemplate.getMessageConverters();
        // 原有的String是ISO-8859-1编码移除
        httpMessageConverterList.removeIf(converter -> converter instanceof StringHttpMessageConverter);
        GsonHttpMessageConverter gsonConverter = new GsonHttpMessageConverter();
        List<MediaType> mediaTypeList = new ArrayList<>();
        // 设置传参为 JSON 类型
        mediaTypeList.add(MediaType.APPLICATION_JSON);

        gsonConverter.setSupportedMediaTypes(mediaTypeList);
        gsonConverter.setGson(GsonUtil.gson());
        // 解决String报错问题：设置编码为 UTF-8
        httpMessageConverterList.add(0, new StringHttpMessageConverter(StandardCharsets.UTF_8));
        httpMessageConverterList.add(1, gsonConverter);
        return restTemplate;
    }

    private OkHttpClient okHttpClient() {
        return new OkHttpClient.Builder()
                .sslSocketFactory(Objects.requireNonNull(sslSocketFactory()), SimpleX509TrustManager.INSTANCE)
                .hostnameVerifier(SimpleHostnameVerifier.INSTANCE)
                .retryOnConnectionFailure(true)
                // 设置连接池参数
                .connectionPool(new ConnectionPool(okHttpRestTemplateProperties.getMaxIdleConnections(), okHttpRestTemplateProperties.getKeepAliveDuration(), TimeUnit.MINUTES))
                .connectTimeout(okHttpRestTemplateProperties.getConnectTimeout(), TimeUnit.SECONDS)
                .readTimeout(okHttpRestTemplateProperties.getReadTimeout(), TimeUnit.SECONDS)
                .writeTimeout(okHttpRestTemplateProperties.getWriteTimeout(), TimeUnit.SECONDS)
                .build();
    }

    private SSLSocketFactory sslSocketFactory() {
        try {
            // 信任任何链接
            SSLContext sslContext = SSLContext.getInstance("TLS");
            sslContext.init(null, new TrustManager[]{SimpleX509TrustManager.INSTANCE}, new SecureRandom());
            return sslContext.getSocketFactory();
        } catch (NoSuchAlgorithmException | KeyManagementException e) {
            e.printStackTrace();
        }
        return null;
    }

    private static class SimpleHostnameVerifier implements HostnameVerifier {

        /**
         * 需要进行校验的 hostname
         */
        private static final Set<String> VERIFY_HOST_NAME_SET = Collections.emptySet();
        private static final SimpleHostnameVerifier INSTANCE = new SimpleHostnameVerifier();

        @Override
        public boolean verify(String hostname, SSLSession sslSession) {
            if (StrUtil.isEmpty(hostname)) {
                return false;
            }
            return !VERIFY_HOST_NAME_SET.contains(hostname);
        }
    }

    private static class SimpleX509TrustManager implements X509TrustManager {

        private static final SimpleX509TrustManager INSTANCE = new SimpleX509TrustManager();

        @Override
        public void checkClientTrusted(X509Certificate[] x509Certificates, String s) {
        }

        @Override
        public void checkServerTrusted(X509Certificate[] x509Certificates, String s) {
        }

        @Override
        public X509Certificate[] getAcceptedIssuers() {
            return new X509Certificate[0];
        }
    }
}
